feat(gateway): SOVD entity status and lifecycle control endpoints#437
Merged
Conversation
…until a provider lands)
…lers
- Change handle_transition parameter from std::string to std::string_view
(performance-unnecessary-value-param: parameter was only read, not modified)
- Replace std::move on trivially-copyable http::NoContent with direct
construction via std::make_pair(http::NoContent{}, std::move(att))
(performance-move-const-arg: move on trivial struct is a no-op)
Replace non-fatal EXPECT_TRUE + unconditional value() access with an ADD_FAILURE guard that returns an empty json on error, eliminating UB (empty tl::expected dereference) on a failing path.
Add GET:/api/v1/{apps,components}/*/status to VIEWER, OPERATOR, CONFIGURATOR lists.
Add PUT:/api/v1/{apps,components}/*/status/* to OPERATOR and CONFIGURATOR lists.
ADMIN is already covered by PUT:/api/v1/**.
Add test_lifecycle.test.py: launch_testing integration test exercising
GET /apps/{id}/status and GET /components/{id}/status (expects "ready"
for online entities) and PUT /apps/{id}/status/restart (expects 501
when no lifecycle provider registered). Host component id resolved
dynamically via GET /components items[0]. REQ_INTEROP_076 marked
verified.
Add design/lifecycle.rst covering the 6 status/lifecycle routes (GET status + 5 PUT transitions for apps and components), status read semantics per entity type, the LifecycleProvider seam, and error mapping (403/409/501). Note that REQ_INTEROP_076 is verified and 077-081 remain open until a substrate plugin lands. Add lifecycle to the gateway design toctree. Update README with the new endpoint list and a SOVD compliance table.
…es and cover provider-present path - Add .response(202, ...) to all 10 PUT transition routes (apps + components) so the published OpenAPI reflects the actual 202 Accepted response instead of the default 200 from the DTO template. - Add .operation_id(...) to all 12 lifecycle routes (2 GET + 10 PUT) following the camelCase verb+Entity+Resource pattern used throughout rest_server.cpp. - Fix misleading comment claiming PUT/GET ordering prevents shadowing; GET and PUT are different methods and cannot shadow each other. - Add LifecycleHandlersWithProviderTest fixture with 3 new test cases covering the provider-present handler path: URI filling on GET status, 202+Location on accepted transition, and 409 mapping for PreconditionFailed provider errors.
The lifecycle status routes are tagged "Lifecycle" but the tag was not registered in the global OpenAPI tags array, so the tag-consistency check in the health integration test failed.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds SOVD ISO 17978-3 entity lifecycle status and control contract to ros2_medkit_gateway, exposing /status endpoints for Apps and Components, with control transitions delegated to a new plugin interface (returning 501 until implemented by a substrate plugin).
Changes:
- Introduces
LifecycleStatusResponseDTO +LifecycleProviderinterface, and routesGET /{apps|components}/{id}/status+PUT /.../status/{start|restart|force-restart|shutdown|force-shutdown}. - Wires lifecycle provider routing through
PluginManager, and advertises astatuslink on app/component detail responses. - Updates RBAC defaults for status read/control, adds unit + integration coverage, and adds design/requirements/README documentation.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/ros2_medkit_integration_tests/test/features/test_lifecycle.test.py | Launch-testing coverage for GET status (ready) and PUT transition (501). |
| src/ros2_medkit_gateway/test/test_lifecycle_provider_routing.cpp | Unit tests for PluginManager lifecycle-provider resolution by entity ownership. |
| src/ros2_medkit_gateway/test/test_lifecycle_handlers.cpp | Unit tests for lifecycle handlers (default path, provider path, error mapping, link advertisement). |
| src/ros2_medkit_gateway/test/test_lifecycle_dto.cpp | DTO JSON writer tests incl. hyphenated transition keys. |
| src/ros2_medkit_gateway/test/test_auth_config.cpp | RBAC unit tests for status/lifecycle permissions. |
| src/ros2_medkit_gateway/src/plugins/plugin_manager.cpp | Adds lifecycle provider caching and lookup by owned entity. |
| src/ros2_medkit_gateway/src/openapi/capability_generator.cpp | Adds “Lifecycle” capability to the root capability list. |
| src/ros2_medkit_gateway/src/http/rest_server.cpp | Registers lifecycle routes and instantiates LifecycleHandlers. |
| src/ros2_medkit_gateway/src/http/handlers/lifecycle_handlers.cpp | Implements GET status and PUT transition logic (default cache-derived status + provider delegation). |
| src/ros2_medkit_gateway/src/http/handlers/discovery_handlers.cpp | Advertises status URI on component/app detail responses. |
| src/ros2_medkit_gateway/src/core/auth/auth_config.cpp | Adds default role permissions for GET status (viewer+) and PUT transitions (operator+). |
| src/ros2_medkit_gateway/README.md | Documents new endpoints and updates SOVD compliance table entries. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/dto/registry.hpp | Registers lifecycle DTO in the global DTO registry. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/dto/lifecycle.hpp | Adds LifecycleStatusResponse DTO contract. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/dto/enums.hpp | Adds allowed lifecycle status values. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/dto/entities.hpp | Adds status link field to app/component detail DTOs. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/providers/lifecycle_provider.hpp | Defines LifecycleProvider interface + provider error struct/enum. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/plugins/plugin_manager.hpp | Extends loaded-plugin provider pointers with lifecycle provider. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/rest_server.hpp | Adds lifecycle_handlers_ member to RESTServer. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/handlers/lifecycle_handlers.hpp | Declares lifecycle handler class. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/handlers/handlers.hpp | Exposes lifecycle handlers via the convenience include. |
| src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/error_codes.hpp | Adds SOVD-standard error codes used by lifecycle provider mapping. |
| src/ros2_medkit_gateway/design/lifecycle.rst | Design doc describing routing, semantics, provider seam, and requirement coverage. |
| src/ros2_medkit_gateway/design/index.rst | Adds lifecycle doc to the design-doc index. |
| src/ros2_medkit_gateway/CMakeLists.txt | Builds new handler + adds new test targets. |
| docs/requirements/specs/lifecycle.rst | Marks REQ_INTEROP_076 as verified. |
mfaferek93
reviewed
Jun 19, 2026
- Log plugin exceptions server-side and return a static message from the lifecycle handler instead of forwarding the exception text to the client, matching the operation/data/fault provider-delegation convention. - Advertise the status sub-resource in the entity capabilities array (add Capability::STATUS) so the top-level URIs and the capabilities array describe the same set of collections. - Add handler unit tests for the provider-error mappings (403, provider-driven 501, clamped 5xx) and the GET provider-error path. - Assert error_code values (not-implemented, entity-not-found) in the lifecycle integration test instead of only checking the field is present. - Fix the design doc: route registration order is irrelevant because GET and PUT are different HTTP methods; use "verified" not "closed" for the status.
- Resolve LifecycleProvider via an extern "C" get_lifecycle_provider query in
the plugin ABI instead of dynamic_cast across the dlopen boundary, where RTTI
is unreliable - a real provider could cast to null and silently never
register, leaving lifecycle control at 501 forever.
- Derive Component readiness from a real liveness signal: the host Component is
ready while reachable; any other Component is ready when at least one hosted
App is online. host_metadata is only a host marker, not a liveness signal.
- Map a provider's EntityNotFound to 404 entity-not-found instead of a 500
plugin-error.
- Validate the provider-supplied status against {ready, notReady} before
writing it, guarding the SOVD contract against a misbehaving plugin.
- Gate the destructive shutdown / force-shutdown transitions behind the
configurator role; operator keeps start / restart / force-restart.
- Add the <optional> include to the lifecycle DTO, scope the RBAC status
comments to apps and components, and assert exactly one host Component in the
integration test.
- Document the status and transition endpoints in docs/api/rest.rst.
- Tests for the provider ABI path, component liveness, the 404 mapping, the
invalid-status guard, and the per-transition RBAC.
mfaferek93
approved these changes
Jun 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the SOVD ISO 17978-3 entity status and lifecycle endpoints to the gateway core:
GET /{apps|components}/{id}/status- returnsready/notReadyplus the URIs of thetransitions the entity supports.
PUT /{apps|components}/{id}/status/{start,restart,force-restart,shutdown,force-shutdown}.This is the core contract only. Status read works from runtime state: an App is
readywhen itsnode is online, and the host Component is
readywhile it is reachable. The five controltransitions are routed to a new per-entity
LifecycleProviderinterface that has no implementationin this PR, so control returns
501until a substrate-owning plugin registers a provider. Theactuation work (ROS lifecycle nodes, process / container / systemd, host reboot) is out of scope
here and is tracked in follow-up issues #434, #435, #436.
Also in this PR:
LifecycleProviderinterface plus aLifecycleProviderErrorInfoerror struct, routed throughPluginManagerlike the existing operation provider.LifecycleStatusResponseDTO, and astatuslink advertised on the app and component detailresponses.
GETstatus is allowed for the viewer role and above; the PUT transitions for operator andabove.
REQ_INTEROP_076marked verified (077-081 stay open since control is a501stub).Making the required role per endpoint configurable is tracked separately in #432. The version bump
is left to a maintainer at release time. No breaking changes; everything here is additive.
Issue
Type
Testing
Unit (GTest):
force-restart,force-shutdown).PluginManager(owned vs unowned entity).ready/notReadyfor apps and the hostcomponent), 404 before 501 for an unknown entity, the five PUT transitions returning
501with noprovider, and the provider-present path via a mock provider (filled transition URIs,
202+Location, andprecondition-not-fulfilled-> 409).Integration (launch_testing):
GET /apps/{id}/statusandGET /components/{host}/statusreturnready, andPUT /apps/{id}/status/restartreturns501. Tagged@verifies REQ_INTEROP_076.clang-tidy is clean on the changed files.
Checklist